#pragma warning(disable : 4355)
#include "Avalon/IO/SizeDeclarativeReader.hpp"
#include <boost/bind.hpp>
#include "Avalon/IO/Buffer.hpp"

using namespace Avalon;
using namespace Avalon::IO;
using namespace Avalon::Threading;
using namespace boost;
using namespace std;

namespace {
  static const unsigned int SIZE_OF_INT = 4;
}

SizeDeclarativeReader::SizeDeclarativeReader(Carry<Reader> source)
    : m_source(source),
      m_sizeRemaining(0) {
  Async<int>::State readState;
  m_sizeRead.SetFinishedSlot(bind(&SizeDeclarativeReader::OnSizeRead, this),
    Store(readState));
  m_dataRead.SetFinishedSlot(bind(&SizeDeclarativeReader::OnDataRead, this),
    Store(readState));
}

SizeDeclarativeReader::~SizeDeclarativeReader() {}

void SizeDeclarativeReader::Read(void* destination, int size,
    Out<Async<int> > sizeRead) {
  m_result = sizeRead.Get();
  if(m_sizeRemaining != 0) {
    boost::uint32_t sizeToRead = min(static_cast<boost::uint32_t>(size),
      m_sizeRemaining);
    m_dataReadParameters.m_destination = destination;
    m_dataReadParameters.m_sizeToRead = sizeToRead;
    m_dataReadParameters.m_totalSize = sizeToRead;
    m_source->Read(destination, static_cast<int>(sizeToRead),
      Store(m_dataRead));
    return;
  }
  m_sizeReadIterator = reinterpret_cast<char*>(&m_sizeRemaining);
  m_sizeReadParameters.m_buffer = NULL;
  m_sizeReadParameters.m_destination = destination;
  m_sizeReadParameters.m_size = size;
  m_source->Read(m_sizeReadIterator, SIZE_OF_INT, Store(m_sizeRead));
}

void SizeDeclarativeReader::Read(Buffer* destination, int size,
    Out<Async<int> > sizeRead) {
  m_result = sizeRead.Get();
  if(m_sizeRemaining != 0) {
    int copyOffset = destination->GetSize();
    int sizeToRead = min(size, static_cast<int>(m_sizeRemaining));
    destination->Grow(sizeToRead);
    m_dataReadParameters.m_destination = destination->GetMutableData() +
      copyOffset;
    m_dataReadParameters.m_sizeToRead = sizeToRead;
    m_dataReadParameters.m_totalSize = sizeToRead;
    m_source->Read(destination, sizeToRead, Store(m_dataRead));
    return;
  }
  m_sizeReadIterator = reinterpret_cast<char*>(&m_sizeRemaining);
  m_sizeReadParameters.m_buffer = destination;
  m_sizeReadParameters.m_destination = NULL;
  m_sizeReadParameters.m_size = size;
  m_source->Read(m_sizeReadIterator, SIZE_OF_INT, Store(m_sizeRead));
}

void SizeDeclarativeReader::OnSizeRead() {
  if(m_sizeRead.GetState() == Async<int>::EXCEPTION) {
    exception_ptr exceptionCopy = m_sizeRead.GetException();
    m_sizeRead.ResetResult();
    Async<int>* result = m_result;
    m_result = NULL;
    m_sizeReadParameters.m_buffer = NULL;
    m_sizeReadParameters.m_destination = NULL;
    result->SetException(exceptionCopy);
    return;
  }
  m_sizeReadIterator += m_sizeRead.Get();
  m_sizeRead.ResetResult();
  if(m_sizeReadIterator < reinterpret_cast<char*>(&m_sizeRemaining) +
      SIZE_OF_INT) {
    ptrdiff_t sizeRemaining = (reinterpret_cast<char*>(&m_sizeRemaining) +
      SIZE_OF_INT) - m_sizeReadIterator;
    m_source->Read(m_sizeReadIterator, static_cast<int>(sizeRemaining),
      Store(m_sizeRead));
    return;
  }
  m_sizeRemaining = FromLittleEndian(m_sizeRemaining);
  int sizeToRead = min(static_cast<int>(m_sizeRemaining),
    m_sizeReadParameters.m_size);
  if(m_sizeReadParameters.m_buffer != NULL) {
    int copyOffset = m_sizeReadParameters.m_buffer->GetSize();
    m_sizeReadParameters.m_buffer->Grow(sizeToRead);
    m_sizeReadParameters.m_destination =
      m_sizeReadParameters.m_buffer->GetMutableData() + copyOffset;
  }
  m_dataReadParameters.m_destination = m_sizeReadParameters.m_destination;
  m_dataReadParameters.m_sizeToRead = sizeToRead;
  m_dataReadParameters.m_totalSize = sizeToRead;
  m_source->Read(m_dataReadParameters.m_destination, sizeToRead,
    Store(m_dataRead));
}

void SizeDeclarativeReader::OnDataRead() {
  if(m_dataRead.GetState() == Async<int>::EXCEPTION) {
    exception_ptr exceptionCopy = m_dataRead.GetException();
    m_dataRead.ResetResult();
    Async<int>* result = m_result;
    m_result = NULL;
    m_sizeReadParameters.m_buffer = NULL;
    m_sizeReadParameters.m_destination = NULL;
    m_dataReadParameters.m_destination = NULL;
    result->SetException(exceptionCopy);
    return;
  }
  m_dataReadParameters.m_sizeToRead -= m_dataRead.Get();
  m_sizeRemaining -= m_dataRead.Get();
  m_dataRead.ResetResult();
  if(m_dataReadParameters.m_sizeToRead == 0) {
    Async<int>* result = m_result;
    m_result = NULL;
    m_sizeReadParameters.m_buffer = NULL;
    m_sizeReadParameters.m_destination = NULL;
    m_dataReadParameters.m_destination = NULL;
    result->SetResult(m_dataReadParameters.m_totalSize);
    return;
  }
  m_source->Read(static_cast<char*>(m_dataReadParameters.m_destination) +
    (m_dataReadParameters.m_totalSize - m_dataReadParameters.m_sizeToRead),
    m_dataReadParameters.m_sizeToRead, Store(m_dataRead));
}
